﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace AI.Graph {


    public abstract class State : CollapsibleNode {

#region Fields

        private static NodeColors s_colors = new NodeColors (new Color32 (0, 185, 165, 255), new Color32 (0, 90, 85, 255), new Color32 (0, 65, 60, 255));

        [SerializeField, HideInInspector]
        [Port (Direction.Input, Capacity.Multi, "Input")]
        private ConnectionPort m_input = null;

        [SerializeField, HideInInspector]
        [Port (Direction.Output, Capacity.Multi, "Output", typeof (Condition), typeof (State))]
        private ConnectionPort m_output = null;

        [SerializeField, HideInInspector]
        [Port (Direction.Output, Capacity.Multi, "Enter", typeof (SubCondition), typeof (SubState))]
        private ConnectionPort m_onEnterOutput = null;

        [SerializeField, HideInInspector]
        [Port (Direction.Output, Capacity.Multi, "Update", typeof (SubCondition), typeof (SubState))]
        private ConnectionPort m_onUpdateOutput = null;

        [SerializeField, HideInInspector]
        [Port (Direction.Output, Capacity.Multi, "Exit", typeof (SubCondition), typeof (SubState))]
        private ConnectionPort m_onExitOutput = null;

        [SerializeField, HideInInspector]
        internal bool isBottomExpanded;

#endregion


#region Properties

        internal override NodeColors colors {
            get { return s_colors; }
        }

        internal override Vector2 position {
            set {
                Vector2 diff = value - position;
                base.position = value;

                foreach (Node node in subNodes) {
                    node.position += diff;
                }
            }
        }

        internal ConnectionPort input {
            get { return m_input; }
        }

        internal ConnectionPort output {
            get { return m_output; }
        }

        internal ConnectionPort onEnterOutput {
            get { return m_onEnterOutput; }
        }

        internal ConnectionPort onUpdateOutput {
            get { return m_onUpdateOutput; }
        }

        internal ConnectionPort onExitOutput {
            get { return m_onExitOutput; }
        }

        public Node[] subNodes {
            get {
                List<Node> nodes = new List<Node> ();

                if (m_onEnterOutput != null) {
                    NodeUtility.FindAllConnectedNodesOnPort (m_onEnterOutput, nodes);
                }

                if (m_onUpdateOutput != null) {
                    NodeUtility.FindAllConnectedNodesOnPort (m_onUpdateOutput, nodes);
                }

                if (m_onExitOutput != null) {
                    NodeUtility.FindAllConnectedNodesOnPort (m_onExitOutput, nodes);
                }

                return nodes.ToArray ();
            }
        }

        
        [SerializeField, HideInInspector]
        internal ActiveTimer activeTimer = new ActiveTimer ();
        
#endregion


        internal override void OnPortDisconnect (ConnectionPort nodePort, ConnectionPort connectedPort) {
            // Enable all hidden connected nodes.
            if (nodePort != m_onEnterOutput && nodePort != m_onUpdateOutput && nodePort != m_onExitOutput) {
                return;
            }

            List<Node> nodes = new List<Node> ();
            NodeUtility.FindAllConnectedNodesOnPort (nodePort, nodes);

            for (int i = 0; i < nodes.Count; i++) {
                nodes[i].isHidden = false;
            }
        }


        /// <summary>
        /// Enter this state.
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        internal void StateEnter (AIBehaviour aiBehaviour) {
            activeTimer.SetActive ();
            
            aiBehaviour.Log (this, $"Enter");
            OnStateEnter (aiBehaviour);
            DoSubStates (aiBehaviour, onEnterOutput.connectedPorts);
        }


        /// <summary>
        /// Update this state.
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        internal void StateUpdate (AIBehaviour aiBehaviour) {
            activeTimer.SetActive ();
            
            aiBehaviour.Log (this, $"Update");
            OnStateUpdate (aiBehaviour);
            DoSubStates (aiBehaviour, onUpdateOutput.connectedPorts);
        }


        /// <summary>
        /// Exit this state.
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        internal void StateExit (AIBehaviour aiBehaviour) {
            activeTimer.SetActive ();
            
            aiBehaviour.Log (this, $"Exit");
            OnStateExit (aiBehaviour);
            DoSubStates (aiBehaviour, onExitOutput.connectedPorts);
        }


        void DoSubStates (AIBehaviour aiBehaviour, ConnectionPort[] connectedPorts) {
            foreach (ConnectionPort port in connectedPorts) {
                if (port.node.IsTypeOf<SubState> ()) {
                    SubState originalSubState = (SubState) port.node;
                    originalSubState.Perform (aiBehaviour);
                }

                if (port.node.IsTypeOf<SubCondition> ()) {
                    SubCondition originalSubCondition = (SubCondition) port.node;
                    originalSubCondition.Decide (aiBehaviour);
                }
            }
        }


        /// <summary>
        /// Is called if the state aiBehaviour enters this State. 
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        protected virtual void OnStateEnter (AIBehaviour aiBehaviour) { }


        /// <summary>
        /// Is called if the state aiBehaviour updates this State. 
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        protected virtual void OnStateUpdate (AIBehaviour aiBehaviour) { }


        /// <summary>
        /// Is called if the state aiBehaviour leaves this State. 
        /// </summary>
        /// <param name="aiBehaviour">The connected AIBehaviour.</param>
        protected virtual void OnStateExit (AIBehaviour aiBehaviour) { }


        /// <summary>
        /// Deep clone with the given original Node.
        /// </summary>
        internal override void Copy (AIControllerSerializer serializer, Node originalNode) {
            base.Copy (serializer, originalNode);
            State original = (State) originalNode;

            m_input = serializer.GetCopyOf (original.m_input);
            m_output = serializer.GetCopyOf (original.m_output);
            m_onEnterOutput = serializer.GetCopyOf (original.m_onEnterOutput);
            m_onUpdateOutput = serializer.GetCopyOf (original.m_onUpdateOutput);
            m_onExitOutput = serializer.GetCopyOf (original.m_onExitOutput);
        }

    }


}